/*
* Copyright 2012 ZerothAngel <zerothangel@tyrannyofheaven.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.tyrannyofheaven.bukkit.util.transaction;
import javax.persistence.PersistenceException;
import com.avaje.ebean.EbeanServer;
/**
* TransactionStrategy that executes the action inside an Avaje Ebean
* transaction. The transaction is committed upon return of the callback.
* To force rollback, throw an exception.
*
* Any PersistenceExceptions caught will cause the transaction to be
* retried (up to maxRetries times).
*
* @author zerothangel
*/
public class RetryingAvajeTransactionStrategy implements TransactionStrategy {
private final EbeanServer ebeanServer;
private final int maxRetries;
private final PreBeginHook preBeginHook;
private final PreCommitHook preCommitHook;
/**
* Create an instance associated with the given EbeanServer.
*
* @param ebeanServer the EbeanServer to use for transactions
* @param maxRetries maximum number of retry attempts (total attempts = maxRetries + 1)
* @param the pre-commit hook or null
*/
public RetryingAvajeTransactionStrategy(EbeanServer ebeanServer, int maxRetries, PreBeginHook preBeginHook, PreCommitHook preCommitHook) {
if (ebeanServer == null)
throw new IllegalArgumentException("ebeanServer cannot be null");
if (maxRetries < 1)
throw new IllegalArgumentException("maxRetries must be > 0");
this.ebeanServer = ebeanServer;
this.maxRetries = maxRetries;
this.preBeginHook = preBeginHook;
this.preCommitHook = preCommitHook;
}
/**
* Create an instance associated with the given EbeanServer.
*
* @param ebeanServer the EbeanServer to use for transactions
* @param maxRetries maximum number of retry attempts (total attempts = maxRetries + 1)
*/
public RetryingAvajeTransactionStrategy(EbeanServer ebeanServer, int maxRetries) {
this(ebeanServer, maxRetries, null, null);
}
// Retrieve the EbeanServer
private EbeanServer getEbeanServer() {
return ebeanServer;
}
// Retrieve the pre-begin hook
private PreBeginHook getPreBeginHook() {
return preBeginHook;
}
// Retrieve the pre-commit hook
private PreCommitHook getPreCommitHook() {
return preCommitHook;
}
/* (non-Javadoc)
* @see org.tyrannyofheaven.bukkit.util.transaction.TransactionStrategy#execute(org.tyrannyofheaven.bukkit.util.transaction.TransactionCallback)
*/
@Override
public <T> T execute(TransactionCallback<T> callback) {
return execute(callback, false);
}
/* (non-Javadoc)
* @see org.tyrannyofheaven.bukkit.util.transaction.TransactionStrategy#execute(org.tyrannyofheaven.bukkit.util.transaction.TransactionCallback, boolean)
*/
@Override
public <T> T execute(TransactionCallback<T> callback, boolean readOnly) {
if (callback == null)
throw new IllegalArgumentException("callback cannot be null");
PersistenceException savedPE = null;
for (int attempt = -1; attempt < maxRetries; attempt++) {
try {
if (getPreBeginHook() != null)
getPreBeginHook().preBegin(readOnly);
getEbeanServer().beginTransaction();
try {
T result = callback.doInTransaction();
if (getPreCommitHook() != null)
getPreCommitHook().preCommit(readOnly);
getEbeanServer().commitTransaction();
return result;
}
finally {
getEbeanServer().endTransaction();
}
}
catch (PersistenceException e) {
savedPE = e;
continue;
}
catch (Error | RuntimeException e) {
// No need to wrap these, just re-throw
throw e;
}
catch (Throwable t) {
throw new TransactionException(t);
}
}
// At this point, we've run out of attempts
throw savedPE;
}
}